﻿using System;
using System.Collections.Generic;
using System.Xml;
using SdmxMl.Common;
using SdmxMl.Structure;
using SdmxMl.Manager;
using SdmxMl.Helper;

namespace GeoPlugIn
{
    public class HgdeSnapshot
    {
        #region Internal members

        private ArtefactManager _armHierarchy;
        private ArtefactManager _armCodelist;
        private HierarchicalCodelistType _historizedHierarchy;
        private Dictionary<string, CodeListType> _dicoCodeList;
        private string _defaultValidFrom;
        private TextProvider msgProvider;

        #endregion //Internal members

        #region Properties

        public ArtefactManager ArmHierarchy
        {
            get { return _armHierarchy; }
        }

        public ArtefactManager ArmCodelist
        {
            get { return _armCodelist; }
        }


        #endregion

        #region Dictionary representation

        private Dictionary<string, CodeListType> GetRefDictionary(SmManager smSnapshot)
        {
            Dictionary<string, CodeListType> dico = new Dictionary<string, CodeListType>();

            foreach (CodelistRefType crt in _historizedHierarchy.CodelistRefList)
            {
                CodeListType clType = null;
                clType = smSnapshot.GetArtefact(new ArtefactRefTyped(SdmxArtefactType.CodeLists, crt.FullIdent)) as CodeListType;
                if (clType != null)
                    dico.Add(crt.Alias, clType);
            }

            return dico;
        }
        #endregion


        #region Construction

        /// <summary>Parametrized C'tor </summary>
        /// <param name="hierarchyArtefact">The artefact holding the historized hierarchy</param>
        public HgdeSnapshot(TextProvider tp, SmManager smSnapshot)
        {
            msgProvider = tp;

            HierarchicalCodelistType hierarchyArtefact = smSnapshot.GetArtefactManager(SdmxArtefactType.HierarchicalCodelists).ArtefactList[0] as HierarchicalCodelistType;

            //pre conditions: The hierarchy must exist.
            _historizedHierarchy = hierarchyArtefact;
            if (_historizedHierarchy == null ||
                _historizedHierarchy.HierarchyList.Count == 0 ||
                _historizedHierarchy.CodelistRefList.Count == 0)
                throw new ApplicationException("Invalid HierarchicalCodelist artefact given as Snapshot Parameter !");

            _dicoCodeList = GetRefDictionary(smSnapshot);
            // The refered codelists must be accessible in memory.
            if (_dicoCodeList.Count != _historizedHierarchy.CodelistRefList.Count)
                throw new ApplicationException("Refered Codelists not accessible in memory!");

            _defaultValidFrom = _historizedHierarchy.ValidFrom;
        }

        #endregion //Construction

        #region Public Methods

        /// <summary>Creates the snapshot at specified date</summary>
        /// <param name="useHistoryCode"></param>
        /// <param name="SnapShotTime">Starting date for SnapShot</param>
        public void Generate(DateTime SnapShotTime, string sdmxVersion)
        {
            _armHierarchy = null;
            // This artefact container will be used in all cases
            _armCodelist = new ArtefactManager(SdmxArtefactType.CodeLists);

            _armHierarchy = GenerateHierarchy(_armCodelist, SnapShotTime, false, sdmxVersion);

        }

        #endregion

        #region Hierarchy Snapshot
        /// <summary>Remove conventional _HIST of historized artegact in id</summary>
        /// <param name="id"></param>/ <returns>Non historized id</returns>
        private string IdWithoutHist(string id)
        {
            string h = "_HIST";

            if (id.Length > h.Length)
            {
                int i = id.IndexOf(h);
                if (i > 0 && i == id.Length - h.Length)
                    return id.Substring(0, i);
            }
            return id;
        }

        private void DatedName(MultiLang ml, DateTime snapshotDate)
        {
            if (ml != null)
            {
                foreach (string lg in LangSupport.SupportedLanguages)
                {
                    ml.UpdateForLanguage(lg, ml.GetIfDefinedForLanguage(lg) + " (" +
                        snapshotDate.ToString("dd.MM.yyyy") + ")");
                }
            }
        }

        private ArtefactManager GenerateHierarchy(ArtefactManager arm, DateTime snapshotDate, bool useHistoryCode, string sdmxVersion)
        {
            ArtefactManager armHierarchy = new ArtefactManager(SdmxArtefactType.HierarchicalCodelists);
            HierarchicalCodelistType he = new HierarchicalCodelistType();
            armHierarchy.ArtefactList.Add(he);
            he.Id = IdWithoutHist(_historizedHierarchy.Id);
            he.AgencyId = _historizedHierarchy.AgencyId;

            foreach (string lg in LangSupport.SupportedLanguages)
                he.Name.UpdateForLanguage(lg,
                    string.Format(msgProvider.GetTextOf(lg, "SNAPSHOT"), snapshotDate.ToString("yyyy-MM-dd")));
            foreach (string lg in LangSupport.SupportedLanguages)
                he.Description.UpdateForLanguage(lg,
                    string.Format(msgProvider.GetDescriptionOf(lg, "SNAPSHOT"), snapshotDate.ToString("yyyy-MM-dd")));

            he.Version = sdmxVersion;
            he.ValidFrom = snapshotDate.ToString("yyyy-MM-dd");

            HierarchyType heType = new HierarchyType(he.CodelistRefList);
            heType.Id = IdWithoutHist(_historizedHierarchy.HierarchyList[0].Id);

            foreach (string lg in LangSupport.SupportedLanguages)
                heType.Name.UpdateForLanguage(lg,
                    string.Format(msgProvider.GetTextOf(lg, "SNAPSHOT"), snapshotDate.ToString("yyyy-MM-dd")));

            heType.LevelList.AddRange(_historizedHierarchy.HierarchyList[0].LevelList.Clone());
            he.HierarchyList.Clear();
            he.HierarchyList.Add(heType);

            foreach (CodelistRefType clrefType in _historizedHierarchy.CodelistRefList)
            {
                // Create references
                CodelistRefType che = new CodelistRefType();
                che.AgencyId = clrefType.AgencyId;
                che.Id = clrefType.Id;
                che.Alias = clrefType.Alias;
                che.Version = sdmxVersion;
                he.CodelistRefList.Add(che);

                // Create One codelist per level
                CodeListType clType = _dicoCodeList[clrefType.Alias];

                CodeListType clt = arm.CreateArtefact() as CodeListType;
                arm.ArtefactList.Add(clt);
                clt.Id = clrefType.Id;
                clt.AgencyId = clrefType.AgencyId;
                clt.Version = sdmxVersion;
                clt.Name = clType.Name.Clone();
                //DatedName(clt.Name, snapshotDate);
                clt.CodeList.HasHierarchy = false;

            }

            // Recurse
            FillHierarchyCodesAt(snapshotDate, _historizedHierarchy.HierarchyList[0].CodeRefList, he.HierarchyList[0].CodeRefList, arm, 0, useHistoryCode == false);

            //Sort if needed
            if (useHistoryCode == false)
                foreach (CodeListType clt in arm.ArtefactList)
                    clt.CodeList.Sort(new ItemCompareNum());

            // Order references also
            OrderHierarchyCodesAt(he.HierarchyList[0].CodeRefList);

            return armHierarchy;
        }

        private void FillHierarchyCodesAt(DateTime snapshotDate, CodeRefTypeList orgList, CodeRefTypeList heList, ArtefactManager arm, int level, bool ofsCode)
        {
            // Nothing more ?
            if (orgList.Count <= 0)
                return;

            CodeListType clt = arm.ArtefactList[level] as CodeListType;
            foreach (CodeRefType crt in orgList)
            {
                if (AddThisCode(crt, snapshotDate))
                {
                    // now fetch code info
                    CodeType code = FetchCode(crt, ofsCode, snapshotDate);
                    clt.CodeList.Add(code);

                    // Crete associated reference item in hierarchy
                    CodeRefType heRefcode = new CodeRefType();
                    heRefcode.CodeID = code.Id;
                    heRefcode.CodelistAliasRef = crt.CodelistAliasRef;
                    heRefcode.ValidFrom = crt.ValidFrom;
                    heRefcode.ValidTo = crt.ValidTo;
                    heList.Add(heRefcode);

                    // Recurse
                    FillHierarchyCodesAt(snapshotDate, crt.CodeRefList, heRefcode.CodeRefList, arm, level + 1, ofsCode);
                }
            }
        }

        private void OrderHierarchyCodesAt(CodeRefTypeList heList)
        {
            if (heList.Count > 1)
                heList.Sort(new CodeRefCompareNum());
            foreach (CodeRefType crt in heList)
                OrderHierarchyCodesAt(crt.CodeRefList);
        }

        #endregion // Hierarchy Snapshot


        #region Snapshot Helpers

        /// <summary>Determines if hierarchy coderef is candicatre for snapshot code</summary>
        /// <param name="crt">the current coderef to analyse</param>
        /// <param name="snapshotDate">the date of snapshot</param>
        /// <returns>true if has to be part of snapshot</returns>
        private bool AddThisCode(CodeRefType crt, DateTime snapshotDate)
        {
            bool addCode = false;
            DateTime dtFrom = DateTime.MaxValue;

            if (string.IsNullOrEmpty(crt.ValidFrom) == false)
                dtFrom = Convert.ToDateTime(crt.ValidFrom);
            else if (string.IsNullOrEmpty(_defaultValidFrom) == false)
                dtFrom = Convert.ToDateTime(_defaultValidFrom);

            addCode = snapshotDate >= dtFrom;

            if (string.IsNullOrEmpty(crt.ValidTo) == false && addCode)
            {
                DateTime dtTo = Convert.ToDateTime(crt.ValidTo);
                addCode = snapshotDate <= dtTo;
            }

            // Special case validfrom same as validto, we must ignore
            if (addCode && crt.ValidFrom.Length > 0)
                if (crt.ValidFrom == crt.ValidTo)
                    addCode = false;

            return addCode;
        }

        /// <summary>From reference, clone the code and adapts it's attributes to snapshot options</summary>
        /// <param name="crt">reference to code</param> 
        /// <param name="ofsCode">if true use code instead of history code</param>
        /// <param name="snapshotDate"></param>
        /// <returns>a new instance of code</returns>
        private CodeType FetchCode(CodeRefType crt, bool ofsCode, DateTime snapshotDate)
        {
            CodeType code = new CodeType();
            CodeListType clType = _dicoCodeList[crt.CodelistAliasRef];

            // Get refered code
            ISdmxHierarchyListItem item = clType.CodeList.LocateItem(crt.CodeID);
            if (item == null)
                throw new ApplicationException("Refered code not found in codelist.");

            // Clone and adapt content
            string codeId = item.Id;
            // PRA-PY    code.AnnotationList = item.AnnotationList.Clone();

            // If an annotation of type "CODE_OFS" exists replace item code (history) by the text of the annotation
            if (ofsCode)
            {
                string ofs = item.AnnotationList.GetTextOfAnnotationType("CODE_OFS");
                if (string.IsNullOrEmpty(ofs) == false)
                {
                    codeId = ofs;
                    // And create annotation for OFS_History number
                    Annotation ann = new Annotation("CODE_HISTORY");
                    ann.Text = new MultiLang(item.Id);
                    code.AnnotationList.Add(ann);
                }

                //PRA-PY code.AnnotationList.RemoveAnnotationType("CODE_OFS");

            }

            //PRA-PY Provides annotation of type ABBREV
            Annotation annAbbrev = item.AnnotationList.GetAnnotationType("ABBREV");
            if (annAbbrev != null)
                code.AnnotationList.Add(annAbbrev);


            code.Id = codeId;
            code.Name = item.Name.Clone();

            // Some code have the description that slightly evolve through time
            // if it is the case retrieve description according to snapshot period
            DateTime olderDate = DateTime.MaxValue;
            foreach (Annotation ann in code.AnnotationList)
            {
                if (ann.Type == "OFS_RENAME")
                {
                    DateTime datePrevText = Convert.ToDateTime(ann.Title);
                    if (snapshotDate < datePrevText && datePrevText < olderDate)
                    {
                        code.Name = ann.Text.Clone();
                        olderDate = datePrevText;
                    }
                }
            }
            code.AnnotationList.RemoveAnnotationType("OFS_RENAME");

            return code;
        }

        #endregion //Snapshot Helpers
    }
}
